﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Legend.Collections;

namespace Legend.Threading
{
    public static class ThreadingExtensions
    {
        /// <summary>
        /// Gets an object that holds a write lock until the Disposed-method
        /// is called.
        /// </summary>
        /// <param name="manager">The object that's used to manage synchronization.</param>
        /// <returns>An IDisposable object that holds a write lock.</returns>
        [DebuggerStepThrough]
        public static IDisposable AcquireWriteLock(this ISynchronizationManager manager)
        {
            Require.ThatArgument(manager).Named("manager").IsNotNull();
            
            return new WriteLock(manager);
        }

        /// <summary>
        /// Gets an object that holds a read lock until the Disposed-method
        /// is called.
        /// </summary>
        /// <param name="manager">The object that's used to manage synchronization.</param>
        /// <returns>An IDisposable object that holds a read lock.</returns>
        [DebuggerStepThrough]
        public static IDisposable AcquireReadLock(this ISynchronizationManager manager)
        {
            Require.ThatArgument(manager).Named("manager").IsNotNull();
            
            return new ReadLock(manager);
        }

        /// <summary>
        /// Gets an object that holds an upgradable read lock until the Disposed-method
        /// is called. The lock can be upgraded to a write lock by calling the Upgrade-
        /// method on the returned object.
        /// </summary>
        /// <param name="manager">The object that's used to manage synchronization.</param>
        /// <returns>An IDisposable object that holds an upgradable read lock.</returns>
        [DebuggerStepThrough]
        public static IUpgradableReadLock AcquireUpgradableReadLock(this ISynchronizationManager manager)
        {
            Require.ThatArgument(manager).Named("manager").IsNotNull();

            return new UpgradableReadLock(manager);
        }

        /// <summary>
        /// Gets an object that holds a write lock until the Disposed-method
        /// is called.
        /// </summary>
        /// <param name="synchronized">The synchronized object to hold the lock on.</param>
        /// <returns>An IDisposable object that holds a write lock.</returns>
        [DebuggerStepThrough]
        public static IDisposable AcquireWriteLock(this ISynchronizable synchronized)
        {
            Require.ThatArgument(synchronized).Named("synchronized").IsNotNull();

            return new WriteLock(synchronized.SynchronizationManager);
        }

        /// <summary>
        /// Gets an object that holds a read lock until the Disposed-method
        /// is called.
        /// </summary>
        /// <param name="synchronized">The synchronized object to hold the lock on.</param>
        /// <returns>An IDisposable object that holds a read lock.</returns>
        [DebuggerStepThrough]
        public static IDisposable AcquireReadLock(this ISynchronizable synchronized)
        {
            Require.ThatArgument(synchronized).Named("synchronized").IsNotNull();
            
            return new ReadLock(synchronized.SynchronizationManager);
        }

        /// <summary>
        /// Gets an object that holds an upgradable read lock until the Disposed-method
        /// is called. The lock can be upgraded to a write lock by calling the Upgrade-
        /// method on the returned object.
        /// </summary>
        /// <param name="synchronized">The synchronized object to hold the lock on.</param>
        /// <returns>An IDisposable object that holds an upgradable read lock.</returns>
        [DebuggerStepThrough]
        public static IUpgradableReadLock AcquireUpgradableReadLock(this ISynchronizable synchronized)
        {
            Require.ThatArgument(synchronized).Named("synchronized").IsNotNull();
            
            return new UpgradableReadLock(synchronized.SynchronizationManager);
        }

        
        private class ReadLock
            : IDisposable 
        {
            private ISynchronizationManager manager;
            private bool isDisposed;

            public ReadLock(ISynchronizationManager manager)
            {
                this.isDisposed = false;
                
                this.manager = manager;
                this.manager.EnterReadLock();
            }

            
            [DebuggerStepThrough]
            public void Dispose()
            {
                if (isDisposed) return;

                this.isDisposed = true;
                this.manager.ExitReadLock();
            }

            
        }

        private class WriteLock
            : IDisposable 
        {
            private ISynchronizationManager manager;
            private bool isDisposed;

            public WriteLock(ISynchronizationManager manager)
            {
                this.isDisposed = false;
                
                this.manager = manager;
                this.manager.EnterWriteLock();
            }
            
            [DebuggerStepThrough]
            public void Dispose()
            {
                if (isDisposed) return;

                this.isDisposed = true;
                this.manager.ExitWriteLock();
            }

            
        }

        private class UpgradableReadLock
            : IUpgradableReadLock
        {
            private ISynchronizationManager manager;
            private bool isDisposed;

            public UpgradableReadLock(ISynchronizationManager manager)
            {
                this.isDisposed = false;

                this.manager = manager;
                this.manager.EnterUpgradableReadLock();
            }

            [DebuggerStepThrough]
            public void Dispose()
            {
                if (isDisposed) return;

                this.isDisposed = true;
                this.manager.ExitUpgradableReadLock();
            }

            /// <summary>
            /// Upgrades the lock to a write lock.
            /// </summary>
            [DebuggerStepThrough]
            public IDisposable Upgrade()
            {
                return new WriteLock(manager);
            }
        }
        
    }
}